To begin, you must update the questions object with all the necessary properties to generate your elements.
Next, make some adjustments to your HTML structure by adding another wrapper within the input container for appending extra input fields like radio buttons, checkboxes, and select options.
var questions = [{
question: "What's your full name?",
type: 'text',
name: 'fullname'
}, // TEXT INPUT FIELD
{
question: "What's your gender?",
type: 'radio',
name: 'gender',
values: ['male', 'female', 'other']
}, // RADIO BUTTONS {male, female, other}
{
question: "What's your date of birth?",
type: 'text',
name: 'dob'
}, // TEXT INPUT FIELD WITH id="datepicker"
{
question: "What's your country?",
type: 'select',
name: 'country',
values: ['Canada', 'US', 'Other']
}, // SELECT BOX WITH LIST OF COUNTRIES IN IT
{
question: "Interest in?",
type: 'checkbox',
name: 'interest',
values: ['male', 'female', 'other']
} // CHECKBOXES {male, female, other}
]
// perform actions once all questions have been answered
var onComplete = function() {
var h1 = document.createElement('h1')
h1.appendChild(document.createTextNode('Thanks ' + questions[0].answer + ' for checking out this example!'))
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
// initialization
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() {
//hide the elements that you create so they don't show up when you proceed to next steps
const hideExtraElems = document.querySelectorAll('#inputWrapper span, #inputWrapper select');
hideExtraElems.forEach(elem => {
elem.style.display = 'none';
});
inputLabel.innerHTML = questions[position].question
inputField.type = questions[position].type || 'text'
//add a name attribute so that radio buttons can work
inputField.name = questions[position].name
if (questions[position].type == 'radio' || questions[position].type == 'checkbox') {
//validation for type radio and checkbox
//hide the main input field
inputField.style.visibility = 'hidden';
//loop through each value for
questions[position].values.forEach(item => {
//create a span that will contain your new input fields
let innerWrapper = document.createElement("span");
//style a bit
innerWrapper.style.display = 'flex';
innerWrapper.style.justifyContent = 'space-between';
//add value name inside the span before adding the element itself
let textNode = document.createTextNode(item.toUpperCase());
//append the value name to the span element
innerWrapper.appendChild(textNode);
//create a new input either using createElement or clone your inputField
let newInput = document.getElementById('inputField').cloneNode(true);
//assign attributes accordingly
newInput.type = questions[position].type;
newInput.value = item;
newInput.id = newInput.id + '-' + item;
newInput.style.visibility = 'visible';
newInput.style.width = 'auto';
//append the input field inside the span
innerWrapper.appendChild(newInput);
//append the span inside the wrapper
inputWrapper.appendChild(innerWrapper);
});
} else if (questions[position].type == 'select') {
//validation for select field
//hide the main input field
inputField.style.visibility = 'hidden';
//create the main select box outside the loop
let newSelect = document.createElement("select");
//loop through the options
questions[position].values.forEach(item => {
//create an option element and assign the attributes
let newOption = document.createElement("option");
let textNode = document.createTextNode(item.toUpperCase());
newOption.value = item;
newOption.appendChild(textNode);
//append the final option inside the select
newSelect.appendChild(newOption);
});
//append the select inside the inputWrapper
inputWrapper.appendChild(newSelect);
} else {
//show the inputField
inputField.style.visibility = 'visible';
inputField.value = questions[position].values || ''
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 functions
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;
}
select{
width: 100%;
border: 0;
outline: none;
padding: 1rem 0;
}
.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">
<div id="inputWrapper">
<input id="inputField" required multiple />
<label id="inputLabel"></label>
</div>
<div id="inputProgress"></div>
</div>
</div>
</div>