Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add radio buttons, checkboxes and select field in a multi step javascript form?

I have the following snippet where I am accepting the details via a multi step form. The problem is that currently it is accepting only the text input fields. As you can see that the array of questions contains the type of questions where:

  1. Question no.1 is just fine as text input as it would accept name.
  2. Question no.2 should be a radio button as it would accept gender.
  3. Question no.3 should be text input field again with a datepicker for DOB,
  4. Question no.4 should be a select box for selecting a country from the list and lastly,
  5. Question no.5 should have the input type as checkbox for selected all those user is interested in seeing (male, female, other).

However, I am stuck on implementing this as I am new to JavaScript. How can I implement this?

The concerned part of the JavaScript is:

// load the next question
function putQuestion() {
  inputLabel.innerHTML = questions[position].question
  inputField.type = questions[position].type || 'text'
  inputField.value = questions[position].answer || ''
  inputField.focus()

  // set the progress of the background
  progress.style.width = position * 100 / questions.length + '%'
  previousButton.className = position ? 'ion-android-arrow-back' : 'ion-person'
  showCurrent()
}

Full working snippet.

var questions = [
  {question: "What's your full name?"}, // TEXT INPUT FIELD
  {question: "What's your gender?"}, // RADIO BUTTONS {male, female, other}
  {question: "What's your date of birth?"}, // TEXT INPUT FIELD WITH id="datepicker"
  {question: "What's your country?"}, // SELECT BOX WITH LIST OF COUNTRIES IN IT
  {question: "Interested in?"} // CHECKBOXES {male, female, other}
]

//do something after the questions have been answered
var onComplete = function() {
  var h1 = document.createElement('h1')
  h1.appendChild(document.createTextNode('Thanks ' + questions[0].answer + ' for checking this pen out!'))
  setTimeout(function() {
    register.parentElement.appendChild(h1)
    setTimeout(function() { h1.style.opacity = 1 }, 50)
  }, 1000)
}

;(function(questions, onComplete) {
  var tTime = 100 // transition transform time from #register in ms
  var wTime = 200 // transition width time from #register in ms
  var eTime = 1000 // transition width time from inputLabel in ms

  // init
  if (questions.length == 0) return
  var position = 0
  putQuestion()

  forwardButton.addEventListener('click', validate)
  inputField.addEventListener('keyup', function(e) {
    transform(0, 0) // ie hack to redraw
    if (e.keyCode == 13) validate()
  })

  previousButton.addEventListener('click', function(e) {
    if (position === 0) return
    position -= 1
    hideCurrent(putQuestion)
  })

  // load the next question
  function putQuestion() {
    inputLabel.innerHTML = questions[position].question
    inputField.type = questions[position].type || 'text'
    inputField.value = questions[position].answer || ''
    inputField.focus()

    // set the progress of the background
    progress.style.width = position * 100 / questions.length + '%'
    previousButton.className = position ? 'ion-android-arrow-back' : 'ion-person'
    showCurrent()
  }

  // when submitting the current question
  function validate() {
    var validateCore = function() {      
      return inputField.value.match(questions[position].pattern || /.+/)
    }

    if (!questions[position].validate) questions[position].validate = validateCore
    // check if the pattern matches
    if (!questions[position].validate()) 
      wrong(inputField.focus.bind(inputField))
    else ok(function() {
      // execute the custom end function or the default value set
      if (questions[position].done) questions[position].done()
      else questions[position].answer = inputField.value
        ++position
        // if there is a new question, hide current and load next
        if (questions[position]) hideCurrent(putQuestion)
      else hideCurrent(function() {
        // remove the box if there is no next question
        register.className = 'close'
        progress.style.width = '100%'
        onComplete()
      })
    })
  }
  
  // helper
  function hideCurrent(callback) {
    inputContainer.style.opacity = 0
    inputLabel.style.marginLeft = 0
    inputProgress.style.width = 0
    inputProgress.style.transition = 'none'
    inputContainer.style.border = null
    setTimeout(callback, wTime)
  }

  function showCurrent(callback) {
    inputContainer.style.opacity = 1
    inputProgress.style.transition = ''
    inputProgress.style.width = '100%'
    setTimeout(callback, wTime)
  }

  function transform(x, y) {
    register.style.transform = 'translate(' + x + 'px ,  ' + y + 'px)'
  }

  function ok(callback) {
    register.className = ''
    setTimeout(transform, tTime * 0, 0, 10)
    setTimeout(transform, tTime * 1, 0, 0)
    setTimeout(callback, tTime * 2)
  }
  function wrong(callback) {
    register.className = 'wrong'
    for (var i = 0; i < 6; i++) // shaking motion
      setTimeout(transform, tTime * i, (i % 2 * 2 - 1) * 20, 0)
    setTimeout(transform, tTime * 6, 0, 0)
    setTimeout(callback, tTime * 7)
  }
}(questions, onComplete))
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap');
body {
  margin: 0;
  background: #fbc02d;
  font-family: 'Roboto', sans-serif;
  overflow-x: hidden;
}

h1 {
  position: relative;
  color: #fff;
  opacity: 0;
  transition: .8s ease-in-out;
}

#progress {
  position: absolute;
  background: #c49000;
  height: 100vh;
  width: 0;
  transition: width 0.2s ease-in-out;
}

.center {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}


#register {
  background: #fff;
  position: relative;
  width: 550px;
  box-shadow: 0 16px 24px 2px rgba(0,0,0,0.14), 0 6px 30px 5px rgba(0,0,0,0.12), 0 8px 10px -5px rgba(0,0,0,0.3);
  transition: transform .1s ease-in-out;
}

#register.close {
  width: 0;
  padding: 0;
  overflow: hidden;
  transition: .8s ease-in-out;
  box-shadow: 0 16px 24px 2px rgba(0,0,0,0);
}

#forwardButton {
  position: absolute;
  right: 20px;
  bottom: 5px;
  font-size: 40px;
  color: #fbc02d;
  float: right;
  cursor: pointer;
  z-index: 20
}
#previousButton {
  position: absolute;
  font-size: 18px;
  left: 30px; /* same as padding on container */
  top: 12px;
  z-index: 20;
  color: #9e9e9e;
  float: right;
  cursor: pointer;
}
#previousButton:hover {color: #c49000}
#forwardButton:hover {color: #c49000}
.wrong #forwardButton {color: #ff2d26}
.close #forwardButton, .close #previousButton {color: #fff}

#inputContainer {
  position: relative;
  padding: 30px 20px 20px 20px;
  margin: 10px 60px 10px 10px;
  opacity: 0;
  transition: opacity .3s ease-in-out;
}

#inputContainer input {
  position: relative;
  width: 100%;
  border: none;
  font-size: 20px;
  outline: 0;
  background: transparent;
  box-shadow: none;
}

#inputLabel {
  position: absolute;
  pointer-events: none;
  top: 32px; /* same as container padding + margin */
  left: 20px; /* same as container padding */
  font-size: 20px;
  transition: .2s ease-in-out;
}

#inputContainer input:valid + #inputLabel {
  top: 6px;
  left: 42px; /* space for previous arrow */
  margin-left: 0!important;
  font-size: 11px;
  font-weight: normal;
  color: #9e9e9e;
}

#inputProgress {
  border-bottom: 3px solid #fbc02d;
  width: 0;
  transition: width .6s ease-in-out;
}

.wrong #inputProgress {
  border-color: #ff2d26;
}

@media (max-width: 420px) {
  #forwardButton {right: 10px}
  #previousButton {left: 10px}
  #inputLabel {left: 0}
  #inputContainer {padding-left: 0; margin-right:20px}
}
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<div id="progress"></div>
<div class="center">
  <div id="register"> 
    
    <i id="previousButton" class="ion-android-arrow-back"></i> 
    <i id="forwardButton" class="ion-android-arrow-forward"></i>
    
    <div id="inputContainer">
      <input id="inputField" required multiple />
      <label id="inputLabel"></label>
      <div id="inputProgress"></div>
    </div>
    
  </div>
</div>

I know I need to give a minimum reproducible example with limited code but I thought creating a working snippet would give a good idea of this micro project. Any help would be appreciated. Thanks in advance :)

like image 661
Relaxing Music Avatar asked Jul 15 '21 15:07

Relaxing Music


People also ask

Can radio buttons have multiple selections?

Radio buttons allow a user to select a single option among multiple options. You can set the Choice Value of each option, for each button, as well as group these buttons by giving them the same Group Name.

How can you draw checkbox and radio button in a form in HTML?

Checkbox allows one or many options to be selected. It is created by using HTML <input> tag but type attribute is set to radio. It is also created using HTML <input> tag but type attribute is set to checkbox. It is a single control unit.

How many choices you can select from radio buttons?

With radio buttons, the user can only select one choice.

How are radio and checkbox attributes of the input tag different from each other?

Checkboxes allow the user to choose items from a fixed number of alternatives, while radio buttons allow the user to choose exactly one item from a list of several predefined alternatives.


1 Answers

I tried the best to do what you asked, you will see that I added 3 divs to include your different requests

I also modify your validate condition

Hoping it can help you

var questions = [
  {
    question: "What's your full name?",
    type: "text"
  }, // TEXT INPUT FIELD
  {
    question: "What's your gender?",
    type: "radio",
    response: ['male','female','other']
  }, // RADIO BUTTONS {male, female, other}
  {
    question: "What's your date of birth?",
    type: "date"
  }, // TEXT INPUT FIELD WITH id="datepicker"
  {
    question: "What's your country?",
    type: "select",
    response: ['UK','FR','DE']
  }, // SELECT BOX WITH LIST OF COUNTRIES IN IT
  {
    question: "Interest in?",
    type: "checkbox",
    response: ['male','female','other']
  } // CHECKBOXES {male, female, other}
]

//do something after the questions have been answered
var onComplete = function() {
  var h1 = document.createElement('h1')
  h1.appendChild(document.createTextNode('Thanks ' + questions[0].answer + ' for checking this pen out!'))
  setTimeout(function() {
    register.parentElement.appendChild(h1)
    setTimeout(function() { h1.style.opacity = 1 }, 50)
  }, 1000)
}

;(function(questions, onComplete) {
  var tTime = 100 // transition transform time from #register in ms
  var wTime = 200 // transition width time from #register in ms
  var eTime = 1000 // transition width time from inputLabel in ms

  // init
  if (questions.length == 0) return
  var position = 0
  putQuestion()

  forwardButton.addEventListener('click', validate)
  inputField.addEventListener('keyup', function(e) {
    transform(0, 0) // ie hack to redraw
    if (e.keyCode == 13) validate()
  })

  previousButton.addEventListener('click', function(e) {
    if (position === 0) return
    position -= 1
    hideCurrent(putQuestion)
    $('#gender').hide();
    $('#country').hide();
    $('#interest').hide();
  })

  // load the next question
  function putQuestion() {
    $('#gender').hide();
    $('#country').hide();
    $('#interest').hide();
    $(inputField).attr('readonly',false);
    $(inputField).attr('required',false);
    inputLabel.innerHTML = questions[position].question;
    inputField.type = 'text';
    switch(questions[position].type){
      case "text":
        $(inputField).attr('required',true)
        inputField.value = questions[position].answer || '';
        break;
      case "radio":
        $(inputField).attr('readonly',true)
        inputField.value = '';

        let nbrGender = questions[position].response;
        if( $('#gender').children().length > 0 ){
          $('#gender').children().remove()
        }
        $('#gender').show();
        for( var $i = 0; $i < nbrGender.length; ++$i ){
          let checkedGender = questions[position].answer == questions[position].response[$i] ? 'checked' : '';

          $('#gender').append(
            '<input type="radio" name="onlyone" value="'+questions[position].response[$i]+'" '+checkedGender+'>'+           
            '<label id="text'+$i+'">'+questions[position].response[$i]+'</label>'
          );
        }
        break;
      case "date":
        inputField.type = 'date';
        inputField.value = questions[position].answer;
        break;
      case "select":
        $(inputField).attr('readonly',true)
        inputField.value = '';

        var nbrCountry = questions[position].response;
        if( $('#country').children().length > 0 ){
          $('#country').children().remove()
        }
        $('#country').show();
        var optionCountry;
        for( var $i = 0; $i < nbrCountry.length; ++$i ){
          let selectedCountry = questions[position].answer == questions[position].response[$i] ? 'selected' : '';
          optionCountry += '<option value="'+questions[position].response[$i]+'" '+selectedCountry+'>'+questions[position].response[$i]+'</option>'
        }
        $('#country').append(' <select name="gender" id="countryList">'+optionCountry+'</select>')
        break;
      case "checkbox":
        $(inputField).attr('readonly',true)
        inputField.value = '';

        let nbrInterest = questions[position].response;
        if( $('#interest').children().length > 0 ){
          $('#interest').children().remove()
        }
        $('#interest').show();

        for( var $i = 0; $i < nbrInterest.length; ++$i ){
          let checkedInterest = questions[position].answer == questions[position].response[$i] ? 'checked' : '';

          $('#interest').append(
            '<input type="checkbox" name="onlyone" value="'+questions[position].response[$i]+'" '+checkedInterest+'>'+           
            '<label id="text'+$i+'">'+questions[position].response[$i]+'</label>'
          );
        }
        break;
      default:
        inputField.type = 'text';
        inputField.value = questions[position].answer || '';
        break;
    }
    inputField.focus()



    // set the progress of the background
    progress.style.width = position * 100 / questions.length + '%'
    previousButton.className = position ? 'ion-android-arrow-back' : 'ion-person'
    showCurrent()
  }

  // when submitting the current question
  function validate() {
    var validateCore = function() {   
      if(inputField.type == "date" && inputField.value ){
        questions[position].answer =inputField.value;
        return true;
      }else if ( inputField.type != "date" ){

        if(inputField.value.match(questions[position].pattern || /.+/)  ){
          questions[position].answer =inputField.value;
          return true;
        } 
        else if(  $('#countryList').is(':visible') && $('#countryList').find(':selected').length > 0 ){
          questions[position].answer = $('#country').find(':selected')[0].value
          return true;
        }
        else if(  $('#gender').is(':visible') && $('#gender').find(':checked').length > 0 ){
          questions[position].answer = $('#gender').find(':checked')[0].value
          return true;
        }else if(  $('#interest').is(':visible') && $('#interest').find(':checked').length > 0 ){
          let responseInterest = [];
          $('#interest').find(':checked').each(function(){
            responseInterest.push($(this).val());
          });
          questions[position].answer = responseInterest;
          return true;
        }   
        else{
          return false;
        }
      }else{
        return false
      }

    }

    if (!questions[position].validate) questions[position].validate = validateCore
    // check if the pattern matches
    if (!questions[position].validate()) 
      wrong(inputField.focus.bind(inputField))
    else ok(function() {
      // execute the custom end function or the default value set
      if (questions[position].done) questions[position].done()

        ++position
        // if there is a new question, hide current and load next
        if (questions[position]) hideCurrent(putQuestion)
      else hideCurrent(function() {
        // remove the box if there is no next question

        register.className = 'close'
        progress.style.width = '100%'
        onComplete()
      })
    })
  }

  // helper
  function hideCurrent(callback) {
    inputContainer.style.opacity = 0
    inputLabel.style.marginLeft = 0
    inputProgress.style.width = 0
    inputProgress.style.transition = 'none'
    inputContainer.style.border = null
    setTimeout(callback, wTime)
  }

  function showCurrent(callback) {
    inputContainer.style.opacity = 1
    inputProgress.style.transition = ''
    inputProgress.style.width = '100%'
    setTimeout(callback, wTime)
  }

  function transform(x, y) {
    register.style.transform = 'translate(' + x + 'px ,  ' + y + 'px)'
  }

  function ok(callback) {
    register.className = ''
    setTimeout(transform, tTime * 0, 0, 10)
    setTimeout(transform, tTime * 1, 0, 0)
    setTimeout(callback, tTime * 2)
  }
  function wrong(callback) {
    register.className = 'wrong'
    for (var i = 0; i < 6; i++) // shaking motion
      setTimeout(transform, tTime * i, (i % 2 * 2 - 1) * 20, 0)
    setTimeout(transform, tTime * 6, 0, 0)
    setTimeout(callback, tTime * 7)
  }
}(questions, onComplete))

// load the next question
function putQuestion() {
  inputLabel.innerHTML = questions[position].question
  inputField.type = questions[position].type || 'text'
  inputField.value = questions[position].answer || ''
  inputField.focus()

  // set the progress of the background
  progress.style.width = position * 100 / questions.length + '%'
  previousButton.className = position ? 'ion-android-arrow-back' : 'ion-person'
  showCurrent()
}
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap');
body {
  margin: 0;
  background: #fbc02d;
  font-family: 'Roboto', sans-serif;
  overflow-x: hidden;
}

h1 {
  position: relative;
  color: #fff;
  opacity: 0;
  transition: .8s ease-in-out;
}

#progress {
  position: absolute;
  background: #c49000;
  height: 100vh;
  width: 0;
  transition: width 0.2s ease-in-out;
}

.center {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}


#register {
  background: #fff;
  position: relative;
  width: 550px;
  box-shadow: 0 16px 24px 2px rgba(0,0,0,0.14), 0 6px 30px 5px rgba(0,0,0,0.12), 0 8px 10px -5px rgba(0,0,0,0.3);
  transition: transform .1s ease-in-out;
}

#register.close {
  width: 0;
  padding: 0;
  overflow: hidden;
  transition: .8s ease-in-out;
  box-shadow: 0 16px 24px 2px rgba(0,0,0,0);
}

#forwardButton {
  position: absolute;
  right: 20px;
  bottom: 5px;
  font-size: 40px;
  color: #fbc02d;
  float: right;
  cursor: pointer;
  z-index: 20
}
#previousButton {
  position: absolute;
  font-size: 18px;
  left: 30px; /* same as padding on container */
  top: 12px;
  z-index: 20;
  color: #9e9e9e;
  float: right;
  cursor: pointer;
}
#previousButton:hover {color: #c49000}
#forwardButton:hover {color: #c49000}
.wrong #forwardButton {color: #ff2d26}
.close #forwardButton, .close #previousButton {color: #fff}

#inputContainer {
  position: relative;
  padding: 30px 20px 20px 20px;
  margin: 10px 60px 10px 10px;
  opacity: 0;
  transition: opacity .3s ease-in-out;
}

#inputContainer input {
  position: relative;
  width: 100%;
  border: none;
  font-size: 20px;
  outline: 0;
  background: transparent;
  box-shadow: none;
}

#inputLabel {
  position: absolute;
  pointer-events: none;
  top: 32px; /* same as container padding + margin */
  left: 20px; /* same as container padding */
  font-size: 20px;
  transition: .2s ease-in-out;
}

#inputContainer input:valid + #inputLabel {
  top: 6px;
  left: 42px; /* space for previous arrow */
  margin-left: 0!important;
  font-size: 11px;
  font-weight: normal;
  color: #9e9e9e;
}

#inputProgress {
  border-bottom: 3px solid #fbc02d;
  width: 0;
  transition: width .6s ease-in-out;
}

.wrong #inputProgress {
  border-color: #ff2d26;
}

@media (max-width: 420px) {
  #forwardButton {right: 10px}
  #previousButton {left: 10px}
  #inputLabel {left: 0}
  #inputContainer {padding-left: 0; margin-right:20px}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<div id="progress"></div>
<div class="center">
  <div id="register"> 

    <i id="previousButton" class="ion-android-arrow-back"></i> 
    <i id="forwardButton" class="ion-android-arrow-forward"></i>

    <div id="inputContainer">
      <input id="inputField" />
      <label id="inputLabel"></label>
      <div id="inputProgress"></div>
      <div id="gender"></div>
      <div id="country"></div>
      <div id="interest"></div>
     

    </div>
 
  </div>
</div>
like image 185
Funky Avatar answered Oct 24 '22 05:10

Funky