I am currently building a window on a website where users can book a boat trip, which requires multiple steps, and 3/5 steps include forms (I only included the first form in the html since it would get too long otherwise - see below).
I am currently handling the validation of the first form, which you can see below (".availability step1"). I've spent quite some time on this validation, however, I can't seem to figure out how to make only the "empty", so the fields that are not valid, take on the error message (.error). Right now it is recognising the ones that are invalid, and I'm getting the CSS connected to invalid (I am getting the red border around the input field), however, I am not getting through the html tag, which is a paragraph that goes underneath the input field.
function init() {
setUpBooking();
}
function setUpBooking(){
formValidation();
}
function formValidation() {
/* ------------ form & elements ----------- */
const form1 = document.querySelector(".availability");
window.form1 = form1;
const elements = form1.elements;
window.elements = elements;
/* --------- delete default validation ------- */
form1.setAttribute("novalidate", true);
/* ------------ custom validation ------------ */
document.querySelector(".next").addEventListener("click", (e) => {
e.preventDefault();
// 1. select all inputs
const formElements = form1.querySelectorAll("input, select");
/* ------------ date ------------ */
if (form1.checkValidity()) {
console.log("form is valid");
// loop through form elements and check if are valid or not
formElements.forEach((el) => {
if (el.checkValidity()) {
el.classList.add("valid");
}
// enable "next" btn when form is valid
var counter = 1, step = "step";
step = ".step" + counter;
if (counter <= 5) {
document.querySelector(step).classList.add("show");
}
counter++;
if (counter > 5) {
counter = 5;
}
step = ".step" + counter; // step is the class and we are appending counter with step so that it looks like the same class in the given class(like counter 1 means step1)
document.querySelector(step).classList.remove("show");
// enable "previous" btn when form is valid
document.querySelector(".previous").addEventListener('click', function () {
if (counter > 1) { // we don't want to remove the first step, it will always be shown
step = ".step" + counter;
document.querySelector(step).classList.add("show");
}
counter--;
if (counter < 1) {
counter = 1;
}
step = ".step" + counter;
document.querySelector(step).classList.remove("show");
});
});
} else {
formElements.forEach((el) => {
if (!el.checkValidity()) {
console.log("form is invalid");
el.classList.add("invalid");
document.querySelector(".error").style.display = "block";
} else {
el.classList.remove("invalid");
}
})
}
})
}
.valid {
border: 1px solid green;
}
.invalid {
border: 1px solid red;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.error {
text-transform: initial;
margin-bottom: 20px;
margin-top: -1px;
border: 1px solid red;
padding: 4px;
z-index: 10;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
display: none;
}
<!-- AVAILABILITY -->
<form class="availability step1">
<label for="date">Choose a date
<input type="date" required>
<p class="error date-err">Please pick a date for your tour</p>
<label for="number">Choose passengers
<input type="number" required>
<p class="error passengers-err">Please pick a number of passengers</p>
</label>
<!-- PERSONAL DATA -->
<form class="personalData step2">
</form>
<!-- ORDER OVERVIEW -->
<div class="orderOverview step3">
</div>
<!-- PAYMENT -->
<form class="payment step4">
</form>
<!-- buttons -->
<button class="previous hide">Previous</button>
<button class="next">Next</button>
Firstly, sorry. I've made some modifications to your forms.
Ideas are as follows:
Appended a span field in every form.
Before going to the next form just check every field is filled in the current form by passing the form number to the validateForm function and check all its input fields are filled.
If yes, return true else return false.
Have a look at the snippet below:
function validateForm(step) {
// console.log(document.forms[step - 1].elements);
var i, l = document.forms[step - 1].elements.length;
for (i = 0; i < l; i++) {
// console.log(document.forms[step - 1].elements[i].value);
if (!document.forms[step - 1].elements[i].value) {
// console.log("All fields should be filled");
document.getElementById("error" + step).textContent = "Fill all the fields please";
document.getElementById("error" + step).style.color = "red";
return false;
}
}
document.getElementById("error" + step).textContent = "Form is completed";
document.getElementById("error" + step).style.color = "green";
return true;
}
var counter = 1,
step = "step";
document.querySelector(".next").addEventListener('click', function() {
step = ".step" + counter;
if (validateForm(counter)) {
if (counter <= 5) {
document.querySelector(step).classList.add("show");
}
counter++;
if (counter > 5) {
counter = 5;
}
step = ".step" + counter; // step is the class and we are appending counter with step so that it looks like the same class in the given class(like counter 1 means step1)
//console.log(step);
document.querySelector(step).classList.remove("show");
}
});
document.querySelector(".previous").addEventListener('click', function() {
if (counter > 1) { // we don't want to remove the first step, it will always be shown
step = ".step" + counter;
//console.log(step);
document.querySelector(step).classList.add("show");
}
counter--;
if (counter < 1) {
counter = 1;
}
step = ".step" + counter;
document.querySelector(step).classList.remove("show");
});
.show {
display: none;
}
<!-- AVAILABILITY -->
<form name="availability" class="availability step1">
<h1>Step1</h1>
<label for="date">Choose a date</label>
<input type="date" name="DATE" required>
<label for="firstname">Enter a firstname</label>
<input type="text" name="FIRSTNAME" required>
<br/>
<span id="error1"> </span>
</form>
<!-- PERSONAL DATA -->
<form class="personalData step2 show">
<h1>Step2</h1>
<label for="date">Choose a date</label>
<input type="date" name="DATE" required>
<label for="firstname">Enter a firstname</label>
<input type="text" name="FIRSTNAME" required>
<br/>
<span id="error2"></span>
</form>
<!-- ORDER OVERVIEW -->
<div class="orderOverview step3 show">
<h1>Step3</h1>
<label for="date">Choose a date</label>
<input type="date" name="DATE" required>
<label for="firstname">Enter a firstname</label>
<input type="text" name="FIRSTNAME" required>
<br/>
<span id="error3"></span>
</div>
<!-- PAYMENT -->
<form class="payment step4 show">
<h1>Step4</h1>
<label for="date">Choose a date</label>
<input type="date" name="DATE" required>
<label for="firstname">Enter a firstname</label>
<input type="text" name="FIRSTNAME" required>
<br/>
<span id="error4"></span>
</form>
<!-- buttons -->
<button class="previous hide">Previous</button>
<button class="next">Next</button>
Related
I have a input field that takes numeric inputs. Then i have a button which display the number of divs as per that input. after displaying div there is two radio-box buttons (paired end and single end) if I select paired end then i want two file upload fields in each divs. and if i select single end then i want only one file upload fields in each div.
I have tried but fileupload fields working on only first div.
function CreateText() {
var text = `<div class="row border-top py-3">
<div class="col-md-3">
<label">sample name *</label></br>
<input type="sample" id="sample" name="sample[]">
</div>
<div class="col-md-3" style="display:none" id="showsingle">
<div class="form-group">
<label for="form_upload">Upload file *</label></br>
<input type="file" id="myFile" name="filename1[]">
</div>
</div>
<div class="col-md-3" style="display:none" id="showpair">
<div class="form-group">
<label for="form_upload">Upload file *</label></br>
<input type="file" id="myFile" name="filename2[]">
<label for="form_upload">Upload file *</label></br>
<input type="file" id="myFile" name="filename2[]">
</div>
</div>
<div class="col-md-3 d-grid">
<div class="form-group">
<button class="btn btn-danger remove_add_btn">Remove</button>
</div>
</div>
</div>`;
var textCount = document.getElementById('textInput').value;
var html = '';
for (var i = 0; i < $('#textInput').val(); i++) {
html = document.getElementById('divDynamicTexts').innerHTML;
document.getElementById('divDynamicTexts').innerHTML = html + text.replace('', i);
}
}
function onlyOne() {
let SradioBox = document.getElementById("singleradio"),
Sfileupload = document.getElementById("showsingle"),
PradioBox = document.getElementById("pairedradio"),
Pfileupload = document.getElementById("showpair");
if (SradioBox.checked == true) {
Sfileupload.style.display = "block",
Pfileupload.style.display = "none";
} else if (PradioBox.checked == true) {
Pfileupload.style.display = "block",
Sfileupload.style.display = "none";
} else {
Pfileupload.style.display = "none",
Sfileupload.style.display = "none";
}
};
$(document).ready(function() {
$(document).on('click', '.remove_add_btn', function(e) {
e.preventDefault();
let row_item = $(this).parent().parent().parent();
$(row_item).remove();
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="text-center">
<input type="text" id="textInput" value="" />
<input type="button" id="" value="Create upload fields" onclick="CreateText();" />
<div class="col-md-4" id="filebutton">
<div class="form-group ">
<label for="form_need">Library Type *</label>
</br>
<div class="px-2">
<label for="myradio">Single end:</label>
<input type="radio" id="singleradio" name="check" onclick="onlyOne();">
<label for="myradio">Paired end:</label>
<input type="radio" id="pairedradio" name="check" onclick="onlyOne();">
</div>
</div>
</div>
</div>
<div id="divDynamicTexts"></div>
ID attributes must be unique. It would be better to remove the IDs altogether ( or change to dataset attributes perhaps ) and use a delegated event listener to process the various clicks related to the task of adding/removing dynamic elements.
In the code below all ID attributes were either removed entirely or changed to data-id type values.
To avoid the need to process different form file input fields at the server the file-input fields are named the same but have an index so can be identified more readily in PHP ( or whatever backend you have )
The delegated listener, because it is bound to the document, will work for all elements whether or not they are static or added dynamically and makes heavy use of the event.target property to help identify the element that invoked the event.
The label element was being used incorrectly previously. If the form-input is within the label then there is no need for the for="ID" syntax ( note that the ID should be the ID of the input element to which the label belongs! ) - as it was the label's appeared to have a for attribute which did not related to an element in the form!
Using querySelector and querySelectorAll you can easily identify nodes of interest within the DOM so button clicks or radio button selection can fire a query to find nodes that are relevant - thus simplifying the hiding/showing of the file input elements.
const strhtml = `
<div data-id="dynrow" class="row border-top py-3">
<div class="col-md-3">
<label>sample name *<input type="text" name="sample[]"></label>
</div>
<div class="col-md-3" style="display:none" data-id="single" data-role="file-field">
<div class="form-group">
<label>Upload file *<input type="file" name="filename[1]" /></label>
</div>
</div>
<div class="col-md-3" style="display:none" data-id="pair" data-role="file-field">
<div class="form-group ">
<label>Upload file *<input type="file" name="filename[1]" /></label>
<label>Upload file *<input type="file" name="filename[2]" /></label>
</div>
</div>
<div class="col-md-3 d-grid">
<div class="form-group">
<button class="btn btn-danger remove_add_btn" data-id='remove'>Remove</button>
</div>
</div>
</div>`;
const _radio = document.querySelectorAll('[type="radio"][data-id]');
const _bttn = document.querySelector('[type="button"][data-id="add"]');
const _div = document.querySelector('#divDynamicTexts');
const _input = document.querySelector('input[type="number"][data-id="textInput"]');
let choice = false;
let qty = false;
/*
Disable radio buttons and the "Create" button initially
and enable when changes are made in the correct sequence.
1: select quantity -> enable radio bttns
2: select single or double -> enable "create" bttn
3: click bttn, create as per radio selection
*/
_input.addEventListener('change', e => {
_radio.forEach(n => {
n.disabled = e.target.value > 0 ? false : true;
});
qty=e.target.value;
});
document.addEventListener('click', e => {
if (e.target instanceof HTMLInputElement && e.target.dataset.id != null) {
/*
set global "choice" variable
and enable "Create" bttn.
*/
if (e.target.type == 'radio') {
choice = e.target.dataset.id;
_bttn.disabled = false;
}
}
/*
If the "choice" has been made the radio
buttons will be enabled. Based on radio
button selected create new HTML and then
unhide the appropriate single/pair DIV
element
*/
if (choice && qty > 0 && e.target.type == 'button') {
_div.innerHTML = '';
for (let i = 0; i < qty; i++) _div.insertAdjacentHTML('afterbegin', strhtml);
let expr = `div[data-id="${choice}"]`;
document.querySelectorAll(expr).forEach(n => n.style.display = 'block');
}
/*
unchanged: delete DIV & contents when "Remove" bttn is clicked.
*/
if (e.target instanceof HTMLButtonElement && e.target.dataset.id != null) {
if (e.target.dataset.id == 'remove') {
_div.removeChild(e.target.closest('[data-id="dynrow"]'));
}
}
});
body {
font-family: arial;
}
label {
display: block;
}
.px-2 {
display: inline-block;
}
.px-2 label {
display: inline-block;
margin: 0.5rem;
}
h2 {
font-size: 1.1rem;
margin: 1rem 0 0 0;
display: inline-block;
width: auto;
}
.inline {
display: inline;
margin: 0 1rem 0 0;
}
#divDynamicTexts {
min-height: 1rem;
margin: 2rem auto
}
div.row {
padding: 0.5rem;
border: 1px dotted grey;
margin: 0.5rem;
}
div[data-id='single'] .form-group label {
background: aliceblue
}
div[data-id='pair'] .form-group label {
background: lightsteelblue
}
div[data-id] .form-group label {
outline: 1px solid grey;
padding: 0.5rem;
margin: 0.5rem 0
}
.bold {
font-weight: bold
}
[disabled]{
border:1px solid red;
outline:2px solid red;
background:rgba(255,0,0,0.25);
}
<div class='text-center'>
<label class='inline bold'>Quantity:<input type='number' data-id='textInput' /></label>
<div class='col-md-4'>
<div class='form-group'>
<h2>Library Type</h2>
<div class='px-2'>
<label>Single end: <input type='radio' data-id='single' name='check' disabled /></label>
<label>Paired end: <input type='radio' data-id='pair' name='check' disabled /></label>
</div>
</div>
</div>
<input type='button' data-id='add' value='Create upload fields' disabled />
</div>
<div id='divDynamicTexts'></div>
i want to have multiple elements with same class that act independently, after 1 night of seeking if "forEach" has any 'forEach:active' i end up with code below, but i feel kind of little shame with 'nextSibling of parent of parent' but if is supported by atleast any modern browsers, then is better than nothing.
on codePen is working fine,as well as snippet here.
i wonder if i can find a better version in vanila js for it or if is there anything deprecated that i should change.
//get + button
const up = document.querySelectorAll('.up');
//tell to + to increase his previous frend value
[].forEach.call(up, function(element) {
element.addEventListener('click', function() {
this.previousElementSibling.value =
parseInt(this.previousElementSibling.value) + 1;
});
})
//get -
const down = document.querySelectorAll('.down');
//tell to - to decrease his next frend value && and hide
//dynamic
//input if == 0 && show firstAdd button
[].forEach.call(down, function(element) {
element.addEventListener('click', function() {
this.nextElementSibling.value =
parseInt(this.nextElementSibling.value) - 1;
if (this.nextElementSibling.value == 0) {
this.parentElement.parentElement.style.display = 'none';
this.parentElement.parentElement.nextElementSibling.style.display = 'initial';
}
});
})
//get firstAdd button
const fAdd = document.querySelectorAll('.firstAdd');
//tell to it to add dynamic input && to vanish itself after &&
//set input value = 1
[].forEach.call(fAdd, function(element) {
element.addEventListener('click', function() {
this.previousElementSibling.style.display = 'initial';
this.previousElementSibling.children[1].children[1].value = 1;
this.style.display = 'none'
});
})
.form-group {
width: 30%;
margin: 30px;
display: none;
}
.input-group {
flex-direction: row;
display: flex;
}
body {
background: #111;
}
<div class='one'>
<div class="form-group">
<label>value: </label>
<div class="input-group">
<button class="down">-</button>
<input type="text" class="myNumber" value='1'>
<button class="up">+</button>
</div>
</div>
<button class='firstAdd'>Add</button></div>
<br>
<div class='two'>
<div class="form-group">
<label>value: </label>
<div class="input-group">
<button class="down">-</button>
<input type="text" class="myNumber" value='1'>
<button class="up">+</button>
</div>
</div>
<button class='firstAdd'>Add</button></div>
I am playing around with some code from the internet to try and create a mock dog walking appointment scheduling application. So far I have my multi-step form which works as should, however I have been trying to move on to starting the submit handling, and realised that as the 'next' button is changed to 'submit' (innerHTML), in the JavaScript, I am not sure where to put the onSubmit() handling functionality..
The challenge is that I am not allowed to use any server side programming, only HTML, CSS, JS and jQuery. Handling the form seems straight-forward enough but I am unsure where to implement the onSubmit() function.
Please go easy on me, it is a university challenge and JS is not my strong point, I have tried looking online but the only suggestions I have are for putting the onSubmit into the button itself, which would be the obvious option but as it's a multi-step form the submit button is not coded into the HTML.
https://codepen.io/caitlinmooneyx/pen/PoGqMaG
HTML
<form id="regForm" name="regForm" action="" class="col-sm-6">
<div class="tab">
<h3>Book your dog walk now</h3>
<!-- BOOKING FORM -->
<div class="row">
<p>Tell us about yourself first..</p>
</div>
<div class="row">
<input type="text" id="fname" placeholder="First Name" name="fname" required>
<input type="text" id="lname" placeholder="Last Name" name="lname" required>
</div>
<div class="row">
<input type="number" id="number" placeholder="Contact Number" name="Number" required>
<input type="email" id="email" placeholder="Email Address" name="email">
</div>
<div class="row">
<p>When should we pick your dog up?</p>
</div>
<div class="row">
<input type="date" id="sdate" class="datepicker" name="sdate" onchange="checkStartDate()" required>
<select name="stime" id="stime" required>
<option value="" selected disabled hidden>Choose a time</option>
<option value="nine">9:00</option>
<option value="hnine">9:30</option>
<option value="ten">10:00</option>
<option value="hten">10:30</option>
<option value="eleven">11:00</option>
<option value="heleven">11:30</option>
<option value="twelve">12:00</option>
<option value="htwelve">12:30</option>
<option value="one">13:00</option>
<option value="hone">13:30</option>
<option value="two">14:00</option>
<option value="htwo">14:30</option>
<option value="three">15:00</option>
</select>
<select name="duration" id="duration" required>
<option value="" selected disabled hidden>Duration</option>
<option value="halfhour">30 mins</option>
<option value="onehour">1 hour</option>
<option value="onehalfhour">1.5 hours</option>
<option value="twohour">2 hours</option>
</select>
</div>
<div class="row">
<p>Where should we pick up/drop off your dog?</p>
</div>
<div class="row">
<div id="locationField">
<input
id="autocomplete"
placeholder="Start typing your address..."
onFocus="geolocate()"
type="text"
required
/>
</div>
</div>
</div>
<div class="tab">
<h3>Now tell us more about your dog..</h3>
<div class="row">
<input type="text" id="dogname" placeholder="Dog Name" name="dogname" required>
</div>
<div class="row">
<p>What breed are they?</p>
</div>
<div class="row">
<select class="breeds"></select>
</div>
<div class="row">
<p>Their favourite places?</p>
</div>
<div class="row">
<input type="checkbox" id="parks" name="parks" value="Parks">
<label for="parks"> Parks</label><br>
<input type="checkbox" id="forests" name="forests" value="Forests">
<label for="forests"> Forests</label><br>
<input type="checkbox" id="beaches" name="beaches" value="Beaches">
<label for="beaches"> Beaches</label>
</div>
<div class="row">
<p>Anything else we should know? (Favourite toys, places to avoid etc)</p>
</div>
<div class="row">
<textarea></textarea>
</div>
</div>
<div style="overflow:auto;">
<div style="float:right;">
<button type="button" id="prevBtn" onclick="nextPrev(-1)">Previous</button>
<button type="button" id="nextBtn" onclick="nextPrev(1)">Next</button>
</div>
</div>
<div style="text-align:center;margin-top:40px;">
<span class="step"></span>
<span class="step"></span>
</div>
</form>
CSS
form p {
margin: 1em 0 .5em 1em;
}
form {
background-color: #fafafa;
padding: 1.5em;
/*margin-right: 6em;*/
}
input, select, textarea {
margin: 1em;
padding: .5em;
width: 45%;
font-size: 17px;
border: 1px solid #aaa;
}
input[type="checkbox"] {
width: auto;
margin: .5em 1em 0 1em;
}
input[type="date"] {
color: #aaa!important;
}
#locationField > input {
width: 290%!important;
}
input.invalid {
background-color: #ffdddd;
}
.tab {
display: none;
}
.step {
height: 15px;
width: 15px;
margin: 0 2px;
background-color: #bbbbbb;
border: none;
border-radius: 50%;
display: inline-block;
opacity: 0.5;
}
.step.active {
opacity: 1;
background-color: #fac123;
}
JS
// ~~~ tab functionality
var currentTab = 0; // current tab is set to be the first tab (0)
showTab(currentTab); // display the current tab
function showTab(n) {
var x = document.getElementsByClassName("tab");
x[n].style.display = "block";
if (n == 0) {
document.getElementById("prevBtn").style.display = "none";
} else {
document.getElementById("prevBtn").style.display = "inline";
}
if (n == (x.length - 1)) {
document.getElementById("nextBtn").innerHTML = "Submit";
} else {
document.getElementById("nextBtn").innerHTML = "Next";
}
fixStepIndicator(n)
}
function nextPrev(n) {
var x = document.getElementsByClassName("tab");
if (n == 1 && !validateForm()) return false;
x[currentTab].style.display = "none";
currentTab = currentTab + n;
if (currentTab >= x.length) {
//...the form gets submitted:
document.getElementById("regForm").submit();
return false;
}
showTab(currentTab);
}
function validateForm() {
var x, y, i, s, valid = true;
x = document.getElementsByClassName("tab");
y = x[currentTab].getElementsByTagName("input");
s = x[currentTab].getElementsByTagName("select");
for (i = 0; i < y.length; i++) {
if (y[i].value == "") {
y[i].className += " invalid";
valid = false;
}
}
for (i = 0; i < s.length; i++) {
if (s[i].value == "") {
s[i].className += " invalid";
valid = false;
}
}
if (valid) {
document.getElementsByClassName("step")[currentTab].className += " finish";
}
return valid; // return the valid status
}
function fixStepIndicator(n) {
var i, x = document.getElementsByClassName("step");
for (i = 0; i < x.length; i++) {
x[i].className = x[i].className.replace(" active", "");
}
x[n].className += " active";
}
// ~~~ dog breed selector
const BREEDS_URL = 'https://dog.ceo/api/breeds/list/all';
const select = document.querySelector('.breeds');
fetch(BREEDS_URL)
.then(res => {
return res.json();
})
.then(data => {
const breedsObject = data.message;
const breedsArray = Object.keys(breedsObject);
for (let i = 0; i < breedsArray.length; i++) {
const option = document.createElement('option');
option.value = breedsArray[i];
option.innerText = breedsArray[i];
select.appendChild(option);
}
console.log(breedsArray);
});
// ~~~ basic form validation
// ~~~ date validation
function checkStartDate() {
var startDate = document.getElementById('sdate').value;
var selectedStartDate = new Date(startDate);
var now = new Date();
if (selectedStartDate < now) {
alert("Start date must be in the future");
$("#sdate").addClass("invalid");
}
}
One solution could be to have a submit button that you hide by default (on the first tab) and show once you go to the second (or last if you add more). This way you won't have to change the innerHTML on any element and just toggle a class. Something like that could look like:
<form id="regForm" name="regForm" action="[NEED ACTION]" class="col-sm-6">
...
<div style="overflow:auto;">
<div style="float:right;">
<button type="button" id="prevBtn" click="nextPrev(-1)">Previous</button>
<button type="button" id="nextBtn" onclick="nextPrev(1)">Next</button>
<submit type="submit" class="hide" value="Submit" />
</div>
</div>
</form>
function showTab(n) {
var x = document.getElementsByClassName("tab");
x[n].style.display = "block";
if (n == 0) {
document.getElementById("prevBtn").style.display = "none";
} else {
document.getElementById("prevBtn").style.display = "inline";
}
if (n == (x.length - 1)) {
document.getElementById("nextBtn").classList.add('hide');
document.getElementById("submitBtn").classList.remove('hide');
} else {
document.getElementById("nextBtn").classList.remove('hide');
document.getElementById("submitBtn").classList.add('hide');
}
fixStepIndicator(n)
}
For this to work you will need to fill in the action property for the form.
Another way you could do this without adding an extra element would be to change the onClick action for the next/submit button.
function showTab(n) {
var x = document.getElementsByClassName("tab");
var nextSubmitBtn = document.getElementById("nextBtn");
x[n].style.display = "block";
if (n == 0) {
document.getElementById("prevBtn").style.display = "none";
} else {
document.getElementById("prevBtn").style.display = "inline";
}
if (n == (x.length - 1)) {
nextSubmitBtn.textContent = "Submit";
nextSubmitBtn.onClick = someSubmitFunc;
} else {
nextSubmitBtn.textContent = "Next";
nextSubmitBtn.onClick = function () { nextPrev(1); };
}
fixStepIndicator(n)
}
This would allow you to keep the same HTML and handle the solution via JS. If you do this remember to keep the current onClick property on the "next" button as that will be the initial function ran (when you first click "next")
A couple tips and notes:
If you are only changing the text of an element (like from "next" to "submit") it would be best to use a function that only changes the text:
// Pure JS
element.textContnet = 'some text';
// jQuery
$element.text('some other text');
this will help prevent possible bugs (and potentially security risk) that can come from innerHTML.
You say you are using jQuery but it is only used in one line in the JS code presented. If that is the only line using jQuery library you could easily replace it and not include the library saving on you site size.
// jQuery way to add class (line 111)
$("#sdate").addClass("invalid");
// Pure JS equivalent
document.getElementById('sdate').classList.add('invalid');
Both will get the job done (add a class) and if you prefer to use jQuery more power to you but if that is the only place you use it then this could be an alternative.
I am working on a form with multiple pages. On one page I have check boxes that I would require at least one to be selected before submission. How can I do that with javascript/html5?
It is all one form that changes pages with javascript. How on the last page can I have the form check, if at least one checkbox has been selected?
var currentTab = 0; // Current tab is set to be the first tab (0)
showTab(currentTab); // Display the current tab
function showTab(n) {
// This function will display the specified tab of the form...
var x = document.getElementsByClassName("tab");
x[n].style.display = "block";
//... and fix the Previous/Next buttons:
if (n == 0) {
document.getElementById("prevBtn").style.display = "none";
} else {
document.getElementById("prevBtn").style.display = "inline";
}
if (n == (x.length - 1)) {
document.getElementById("nextBtn").innerHTML = "Submit";
} else {
document.getElementById("nextBtn").innerHTML = "Next";
}
//... and run a function that will display the correct step indicator:
fixStepIndicator(n)
}
function nextPrev(n) {
// This function will figure out which tab to display
var x = document.getElementsByClassName("tab");
// Exit the function if any field in the current tab is invalid:
if (n == 1 && !validateForm()) return false;
// Hide the current tab:
x[currentTab].style.display = "none";
// Increase or decrease the current tab by 1:
currentTab = currentTab + n;
// if you have reached the end of the form...
if (currentTab >= x.length) {
// ... the form gets submitted:
document.getElementById("regForm").submit();
return false;
}
// Otherwise, display the correct tab:
showTab(currentTab);
}
function validateForm() {
// This function deals with validation of the form fields
var x, y, i, valid = true;
x = document.getElementsByClassName("tab");
y = x[currentTab].getElementsByTagName("input");
// A loop that checks every input field in the current tab:
for (i = 0; i < y.length; i++) {
// If a field is empty...
if (y[i].value == "") {
// add an "invalid" class to the field:
y[i].className += " invalid";
// and set the current valid status to false
valid = false;
}
}
// If the valid status is true, mark the step as finished and valid:
if (valid) {
document.getElementsByClassName("step")[currentTab].className += " finish";
}
return valid; // return the valid status
}
function fixStepIndicator(n) {
// This function removes the "active" class of all steps...
var i, x = document.getElementsByClassName("step");
for (i = 0; i < x.length; i++) {
x[i].className = x[i].className.replace(" active", "");
}
//... and adds the "active" class on the current step:
x[n].className += " active";
}
* {
box-sizing: border-box;
}
body {
background-color: #f1f1f1;
}
#regForm {
background-color: #ffffff;
margin: 100px auto;
font-family: Raleway;
padding: 40px;
width: 70%;
min-width: 300px;
}
h1 {
text-align: center;
}
input {
padding: 10px;
width: 100%;
font-size: 17px;
font-family: Raleway;
border: 1px solid #aaaaaa;
}
/* Mark input boxes that gets an error on validation: */
input.invalid {
background-color: #ffdddd;
}
/* Hide all steps by default: */
.tab {
display: none;
}
button {
background-color: #4CAF50;
color: #ffffff;
border: none;
padding: 10px 20px;
font-size: 17px;
font-family: Raleway;
cursor: pointer;
}
button:hover {
opacity: 0.8;
}
#prevBtn {
background-color: #bbbbbb;
}
/* Make circles that indicate the steps of the form: */
.step {
height: 15px;
width: 15px;
margin: 0 2px;
background-color: #bbbbbb;
border: none;
border-radius: 50%;
display: inline-block;
opacity: 0.5;
}
.step.active {
opacity: 1;
}
/* Mark the steps that are finished and valid: */
.step.finish {
background-color: #4CAF50;
}
<form id="regForm" action="/action_page.php">
<h1>Register:</h1>
<!-- One "tab" for each step in the form: -->
<div class="tab">Name:
<p><input placeholder="First name..." oninput="this.className = ''" name="fname"></p>
<p><input placeholder="Last name..." oninput="this.className = ''" name="lname"></p>
</div>
<div class="tab">Contact Info:
<p><input placeholder="E-mail..." oninput="this.className = ''" name="email"></p>
<p><input placeholder="Phone..." oninput="this.className = ''" name="phone"></p>
</div>
<div class="tab">
<H3 id="subhead">AUDITORIUM ROOM SETUP & EQUIPMENT REQUESTS</H3>
<label><label id="asterisk">*</label>Room Setup (Select all that apply):</label><br><br>
<!-- <p><input placeholder="dd" oninput="this.className = ''" name="dd"></p> -->
<br>
<input id="element_3_1" name="Room-Setup" required type="checkbox" value="Auditorium-Seating-for-300" />
<label class="choice" id="labelinput" for="element_3_1">Auditorium Seating for 300</label>
<br>
<input id="element_3_2" name="Room-Setup" class="elementcheckbox" type="checkbox" value="Auditorium-Seating-for-350" />
<label class="choice" id="labelinput" for="element_3_2">Auditorium Seating for 350 (recommended max for Performances</label>
<label class="choice" for="element_3_3">Auditorium Seating 400</label>
<input id="element_3_3" name="Room-Setup" class="elementcheckbox" type="checkbox" value="Auditorium-Seating-400" />
<label class="choice" for="element_3_4">Round Tables and Chairs</label>
<input id="element_3_4" name="Room-Setup" class="elementcheckbox" type="checkbox" value="Round-Tables-and-Chairs" />
<label class="choice" for="element_3_5">Other (Please note that we cannot change the auditorium setup during your event*)</label>
<input id="element_3_5" name="Room-Setup" class="elementcheckbox" type="checkbox" value="Other" />
</div>
<div style="overflow:auto;">
<div style="float:right;">
<button type="button" id="prevBtn" onclick="nextPrev(-1)">Previous</button>
<button type="button" id="nextBtn" onclick="nextPrev(1)">Next</button>
</div>
</div>
<!-- Circles which indicates the steps of the form: -->
<div style="text-align:center;margin-top:40px;">
<span class="step"></span>
<span class="step"></span>
<span class="step"></span>
<span class="step"></span>
</div>
</form>
Put a second classname in <div class="tab theFirstTabWithCheckboxes">
This code get that element and counts cheched inputs to a global variable
var checkboxes = document.querySelectorAll("tab.theFirstTabWithCheckboxes input");
var i = checkboxes.length;
window.count_nr_of_checked_boxes = 0;
while (i--) { //count down to 0
if (checkboxes[i].checked) window.count_nr_of_checked_boxes++;
}
With querySelectorAll() you select in the same way as you select in CSS. Note how the dot operator is an and operator and the space operator means "input is inside ..." (it is evaluated from right to left).
Put the following line in the function validateForm()
if (count_nr_of_checked_boxes < 1) valid = false
You dont need to have window. on global variables
I need a form with multiple steps where the first step restricts options in the successive steps.
So below is an example of use:
User selects from 4 global options
If user selects option 1
Then the user can only select 8 options (no more no less) from a multiple choice box.
If user select option 2
Then the user can only select 10 options (no more no less) from a multiple choice box.
Same is the case with options 3 and 4
After all these selections, this form has to show a final price based on the options user selected in the FIRST STEP. No matter what choices were selected in successive steps.
When this form shows final price, user has to click SUBMIT button and send this result with all these options through an email.
Can someone help me in figuring out the javascript required to do this? Most important thing right now for me is to figure out 'restrictions codes' in multiple items choice boxes.
This is the form I was thinking about:
<form action="" id="menuform" onsubmit="return false;">
<fieldset>
<legend>Select one of this four menus</legend>
<label >Menu Option</label>
<input type="radio" name="selectedmenu" value="Menu1"
onclick="calculateTotal()" />
Menu 1 - serves 8 courses ($20)
<input type="radio" name="selectedmenu" value="Menu2"
onclick="calculateTotal()" />
Menu 2 - serves 12 courses ($25)
<input type="radio" name="selectedmenu" value="Menu3"
onclick="calculateTotal()" />
Menu 3 - serves 16 courses ($35)
<input type="radio" name="selectedmenu" value="Menu4"
onclick="calculateTotal()" />
Menu 4 - serves 30 courses ($75)
<label >Filling</label>
<select id="filling" name='filling'
onchange="calculateTotal()">
<option value="dish1">Dish 1 ($1)</option>
<option value="dish2">Dish 2 ($5)</option>
<option value="dish3">Dish 3 ($5)</option>
(more...)
</select>
<br/>
<div id="totalPrice"></div>
</fieldset>
</form>
Then, for Javascript code I was trying with this to store and return some values but my problem is how to force to select an exact number of options in the SECOND STEP:
var menu_prices = new Array();
menu_prices["Menu1"]=20;
menu_prices["Menu2"]=25;
menu_prices["Menu3"]=35;
menu_prices["Menu4"]=75;
function getMenuPrice()
{
var menuPrice=0;
var theForm = document.forms["menuform"];
var selectedMenu = theForm.elements["selectedmenu"];
for(var i = 0; i < selectedMenu.length; i++)
{
if(selectedMenu[i].checked)
{
menuPrice = menu_prices[selectedMenu[i].value];
break;
}
}
return menuPrice;
}
function getTotal()
{
var menuPrice = getMenuPrice() + getOtherOptionsPrices();
document.getElementById('totalPrice').innerHTML =
"Total Price For Menu $"+menuPrice;
}
Structure your HTML markup carefully, which helps you to target elements via JavaScript easily. Especially, using data-attributes on radio buttons for quantity and price which could be then easily retrieved in respective event handlers. Something like this:
<form action="" method="post" id="menuform" name="menuform">
<fieldset>
<legend>Select menu option</legend>
<label>
<input type="radio" name="selectedmenu" checked value="menu01" data-qty='3' data-price='20' />
<span>1 - serves 3 courses ($20)</span>
</label>
...
<fieldset>
<legend id='fillingsPrompt'>Select fillings</legend>
<select id="fillings" name="fillings" size="6" multiple>
<option value="1">Dish 1 ($1)</option>
...
<fieldset>
<legend>Checkout</legend>
<div id="totalPrice"></div>
<input type="submit" value="Submit" />
</fieldset>
<fieldset>
<legend>Messages</legend>
<p id="result"></p>
</fieldset>
</form>
Identify and select all elements that you'll need:
var menuform = document.getElementById('menuform'),
radios = document.getElementsByName('selectedmenu'),
fillings = document.getElementById('fillings'),
fillingsPrompt = document.getElementById('fillingsPrompt'),
totalPrice = document.getElementById('totalPrice'),
result = document.getElementById('result'),
fillingsAllowed = 0, currentSelection = [], currency = '$'
;
Add event listeners to your radio buttons, select, and the submit button:
menuform.addEventListener('submit', handleSubmit);
fillings.addEventListener('change', handleFillings);
for (var i = radios.length; i--; ) {
radios[i].addEventListener('change', handleLimit);
}
Code the actual event handlers:
// When radio buttons are selected, update parameters for limit
function handleLimit(e) { updateParameters(e.target); }
// When options are selected in the dropdown,
// check against the limit and reset the selection if it exceeds
function handleFillings(e) {
var count = getSelectedCount();
if (count > fillingsAllowed) { resetSelect(); }
else { currentSelection = getSelectedValues(); }
}
// When submit button is clicked,
// check the count of selection against the limit, and
// show appropriate error message
function handleSubmit(e) {
var count = getSelectedCount();
e.preventDefault();
if (count != fillingsAllowed) {
result.textContent = 'Must select exactly ' + fillingsAllowed + ' fillings!';
} else {
result.textContent = 'Ok. ';
}
}
And then code all the helper functions used in the handlers above:
function updateParameters(elem) {
// update the limit based on quantity data attribute on radio
fillingsAllowed = elem.getAttribute('data-qty');
// show the amount based on price data-attribute
totalPrice.textContent = 'Amount: ' + currency + elem.getAttribute('data-price');
// show the hint on legend of fieldset for selecting options
fillingsPrompt.textContent = 'Select ' + fillingsAllowed + ' fillings';
}
// iterate options and get count of selected ones
function getSelectedCount() {
var options = fillings.options, count = 0;
for (var i=0; i < options.length; i++) {
if (options[i].selected) count++;
}
return count;
}
// iterate options and get selected values in an array
function getSelectedValues() {
var options = fillings.options, values = [0];
for (var i=0; i < options.length; i++) {
if (options[i].selected) values.push(options[i].value);
}
return values;
}
// remove selection from all options, and
// re-select based on the array used in the previous function
function resetSelect() {
var options = fillings.options;
for (var i=0; i < options.length; i++) {
options[i].selected = false;
if (currentSelection.indexOf(options[i].value) != -1) {
options[i].selected = true;
}
}
}
Everything put together, the demo looks like this:
Fiddle: https://jsfiddle.net/abhitalks/L813qudw/
Snippet:
var menuform = document.getElementById('menuform'),
radios = document.getElementsByName('selectedmenu'),
fillings = document.getElementById('fillings'),
fillingsPrompt = document.getElementById('fillingsPrompt'),
totalPrice = document.getElementById('totalPrice'),
result = document.getElementById('result'),
fillingsAllowed = 0, currentSelection = [], currency = '$'
;
// listen to events
menuform.addEventListener('submit', handleSubmit);
fillings.addEventListener('change', handleFillings);
for (var i = radios.length; i--; ) {
radios[i].addEventListener('change', handleLimit);
}
// event handlers
function handleLimit(e) { updateParameters(e.target); }
function handleFillings(e) {
var count = getSelectedCount();
if (count > fillingsAllowed) { resetSelect(); }
else { currentSelection = getSelectedValues(); }
}
function handleSubmit(e) {
var count = getSelectedCount();
e.preventDefault();
if (count != fillingsAllowed) {
result.textContent = 'Must select exactly ' + fillingsAllowed + ' fillings!';
} else {
result.textContent = 'Ok. ';
}
}
// fire initial update based on the first radio
updateParameters(radios[0]);
// helper functions
function updateParameters(elem) {
fillingsAllowed = elem.getAttribute('data-qty');
totalPrice.textContent = 'Amount: ' + currency + elem.getAttribute('data-price');
fillingsPrompt.textContent = 'Select ' + fillingsAllowed + ' fillings';
}
function getSelectedCount() {
var options = fillings.options, count = 0;
for (var i=0; i < options.length; i++) {
if (options[i].selected) count++;
}
return count;
}
function getSelectedValues() {
var options = fillings.options, values = [0];
for (var i=0; i < options.length; i++) {
if (options[i].selected) values.push(options[i].value);
}
return values;
}
function resetSelect() {
var options = fillings.options;
for (var i=0; i < options.length; i++) {
options[i].selected = false;
if (currentSelection.indexOf(options[i].value) != -1) {
options[i].selected = true;
}
}
}
fieldset {
margin: 1vw; font-family: monospace;
display: inline-block; width: 40vw; vertical-align: top;
}
legend { color: #d33; padding: 0px 4px; }
legend::before { content: '[ '; }
legend::after { content: ' ]'; }
fieldset > label { display: block; margin: 4px 0px; }
fieldset input, fieldset span { vertical-align: middle; }
fieldset > select { width: 100%; font-family: monospace; }
input[type=submit] { margin-top: 12px; }
#totalPrice, #result {
height: 24px; line-height: 24px;
background-color: #dde; padding: 4px;
font-family: monospace;
}
#result { color: #d33; font-family: monospace; }
<form action="" method="post" id="menuform" name="menuform">
<fieldset>
<legend>Select menu option</legend>
<label>
<input type="radio" name="selectedmenu" checked
value="menu01" data-qty='3' data-price='20' />
<span>1 - serves 3 courses ($20)</span>
</label>
<label>
<input type="radio" name="selectedmenu"
value="menu02" data-qty='4' data-price='25' />
<span>2 - serves 4 courses ($25)</span>
</label>
<label>
<input type="radio" name="selectedmenu"
value="menu03" data-qty='5' data-price='35' />
<span>3 - serves 5 courses ($35)</span>
</label>
<label>
<input type="radio" name="selectedmenu"
value="menu04" data-qty='6' data-price='75' />
<span>4 - serves 6 courses ($75)</span>
</label>
</fieldset>
<fieldset>
<legend id='fillingsPrompt'>Select fillings</legend>
<select id="fillings" name="fillings" size="6" multiple>
<option value="1">Dish 1 ($1)</option>
<option value="2">Dish 2 ($5)</option>
<option value="3">Dish 3 ($5)</option>
<option value="4">Dish 4 ($1)</option>
<option value="5">Dish 5 ($5)</option>
<option value="6">Dish 6 ($5)</option>
</select>
</fieldset>
<fieldset>
<legend>Checkout</legend>
<div id="totalPrice"></div>
<input type="submit" value="Submit" />
</fieldset>
<fieldset>
<legend>Messages</legend>
<p id="result"></p>
</fieldset>
</form>
<hr>
...how can I change <option> and use <input type="checkbox">
instead for the SECOND STEP?
In order to use checkboxes instead of select, no major changes are required.
Changed mark-up:
<fieldset>
<legend id='fillingsPrompt'>Select fillings</legend>
<label>
<input type='checkbox' name='fillings' value="1" />
<span>Dish 1 ($5)</span>
</label>
...
JavaScript changes:
Adding the event-handlers for checkboxes instead of select, would require just iterating over those:
(just like the radios already done)
for (var i = fillings.length; i--; ) {
fillings[i].addEventListener('change', handleFillings);
}
In all the helper functions, remove the variable declaration for options:
(as it is now no longer required)
var options = fillings.options
And, In all the helper functions,
change: options.length and options[i].selected
to, fillings.length and fillings[i].checked respectively.
That's it.
Fiddle 2: https://jsfiddle.net/abhitalks/hp88wdfc/
Snippet 2:
var menuform = document.getElementById('menuform'),
radios = document.getElementsByName('selectedmenu'),
fillings = document.getElementsByName('fillings'),
fillingsPrompt = document.getElementById('fillingsPrompt'),
totalPrice = document.getElementById('totalPrice'),
result = document.getElementById('result'),
fillingsAllowed = 0, currentSelection = [], currency = '$'
;
// listen to events
menuform.addEventListener('submit', handleSubmit);
for (var i = fillings.length; i--; ) {
fillings[i].addEventListener('change', handleFillings);
}
for (var i = radios.length; i--; ) {
radios[i].addEventListener('change', handleLimit);
}
// event handlers
function handleLimit(e) { updateParameters(e.target); }
function handleFillings(e) {
var count = getSelectedCount();
if (count > fillingsAllowed) { resetSelect(); }
else { currentSelection = getSelectedValues(); }
}
function handleSubmit(e) {
var count = getSelectedCount();
e.preventDefault();
if (count != fillingsAllowed) {
result.textContent = 'Must select exactly ' + fillingsAllowed + ' fillings!';
} else {
result.textContent = 'Ok. ';
}
}
// fire initial update based on the first radio
updateParameters(radios[0]);
// helper functions
function updateParameters(elem) {
fillingsAllowed = elem.getAttribute('data-qty');
totalPrice.textContent = 'Amount: ' + currency + elem.getAttribute('data-price');
fillingsPrompt.textContent = 'Select ' + fillingsAllowed + ' fillings';
}
function getSelectedCount() {
var count = 0;
for (var i=0; i < fillings.length; i++) {
if (fillings[i].checked) count++;
}
return count;
}
function getSelectedValues() {
var values = [0];
for (var i=0; i < fillings.length; i++) {
if (fillings[i].checked) values.push(fillings[i].value);
}
return values;
}
function resetSelect() {
for (var i=0; i < fillings.length; i++) {
fillings[i].checked = false;
if (currentSelection.indexOf(fillings[i].value) != -1) {
fillings[i].checked = true;
}
}
}
fieldset {
margin: 1vw; font-family: monospace;
display: inline-block; width: 40vw; vertical-align: top;
}
legend { color: #d33; padding: 0px 4px; }
legend::before { content: '[ '; }
legend::after { content: ' ]'; }
fieldset:first-of-type > label { display: block; margin: 4px 0px; }
fieldset:nth-of-type(2) > label {
display: inline-block; width: 45%;
}
fieldset input, fieldset span { vertical-align: middle; }
input[type=submit] { margin-top: 12px; }
#totalPrice, #result {
height: 24px; line-height: 24px;
background-color: #dde; padding: 4px;
font-family: monospace;
}
#result { color: #d33; font-family: monospace; }
<form action="" method="post" id="menuform" name="menuform">
<fieldset>
<legend>Select menu option</legend>
<label>
<input type="radio" name="selectedmenu" checked
value="menu01" data-qty='3' data-price='20' />
<span>1 - serves 3 courses ($20)</span>
</label>
<label>
<input type="radio" name="selectedmenu"
value="menu02" data-qty='4' data-price='25' />
<span>2 - serves 4 courses ($25)</span>
</label>
<label>
<input type="radio" name="selectedmenu"
value="menu03" data-qty='5' data-price='35' />
<span>3 - serves 5 courses ($35)</span>
</label>
<label>
<input type="radio" name="selectedmenu"
value="menu04" data-qty='6' data-price='75' />
<span>4 - serves 6 courses ($75)</span>
</label>
</fieldset>
<fieldset>
<legend id='fillingsPrompt'>Select fillings</legend>
<label>
<input type='checkbox' name='fillings' value="1" />
<span>Dish 1 ($5)</span>
</label>
<label>
<input type='checkbox' name='fillings' value="2" />
<span>Dish 2 ($5)</span>
</label>
<label>
<input type='checkbox' name='fillings' value="3" />
<span>Dish 3 ($5)</span>
</label>
<label>
<input type='checkbox' name='fillings' value="4" />
<span>Dish 4 ($5)</span>
</label>
<label>
<input type='checkbox' name='fillings' value="5" />
<span>Dish 5 ($5)</span>
</label>
<label>
<input type='checkbox' name='fillings' value="6" />
<span>Dish 6 ($5)</span>
</label>
</fieldset>
<fieldset>
<legend>Checkout</legend>
<div id="totalPrice"></div>
<input type="submit" value="Submit" />
</fieldset>
<fieldset>
<legend>Messages</legend>
<p id="result"></p>
</fieldset>
</form>
<hr>