首页 > 解决方案 > 如何在多步 javascript 表单中添加单选按钮、复选框和选择字段?

问题描述

我有以下片段,我通过多步骤表单接受详细信息。问题是目前它只接受文本输入字段。如您所见,问题数组包含以下问题类型:

  1. 问题 1 可以作为文本输入,因为它可以接受名称。
  2. 问题 2 应该是一个单选按钮,因为它会接受性别。
  3. 问题 3 应该再次是文本输入字段,带有 DOB 的日期选择器,
  4. 问题 4 应该是一个选择框,用于从列表中选择一个国家,最后,
  5. 问题 5 的输入类型应为选中所有用户有兴趣看到的复选框(男性、女性、其他)。

但是,由于我是 JavaScript 新手,所以我坚持实现这一点。我该如何实施?

JavaScript的相关部分是:

// 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()
}

完整的工作片段。

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>

我知道我需要用有限的代码给出一个最小可重复的例子,但我认为创建一个工作片段可以很好地了解这个微型项目。任何帮助,将不胜感激。提前致谢 :)

标签: javascripthtmljquerycss

解决方案


我尽力按照您的要求做,您会看到我添加了 3 个 div 以包含您的不同请求

我还修改了您的验证条件

希望它可以帮助你

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>


推荐阅读